{{!

  Copyright (c) Meta Platforms, Inc. and affiliates.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

}}{{!

This file holds pure python implementations of thrift types that
are re-exported via types.pyx

}}
{{> common/auto_generated_py}}
{{#program:inplace_migrate?}}

from __future__ import annotations

from collections.abc import Mapping, Sequence, Set
import enum
import importlib
from builtins import property as _python__property

import typing as _typing
import folly.iobuf as _fbthrift_iobuf
import thrift.py3.types
from thrift.py3.types import (
    _fbthrift__round_float32,
    _fbthrift__is_float32,
    _fbthrift__filter_kwargs,
)
import thrift.py3.exceptions
import thrift.python.exceptions
import thrift.python.types
import {{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.thrift_types as _fbthrift_python_types
{{! must import thrift_types before thrift_enums due to enum dependence on metadata.thrift_types}}
import {{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.thrift_enums as _fbthrift_python_enums


{{#program:includeNamespaces}}
{{#hasTypes?}}
import {{#includeNamespace}}{{value}}.{{/includeNamespace}}types as _{{#includeNamespace}}{{value}}_{{/includeNamespace}}types
{{#program:hasConstants}}
import {{#includeNamespace}}{{value}}.{{/includeNamespace}}types
{{/program:hasConstants}}
{{/hasTypes?}}
{{/program:includeNamespaces}}

def get_types_reflection():
    return importlib.import_module(
        "{{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.types_reflection"
    )

def _get_py_deprecated_module():
    import thrift.util.converter # needed by _to_py_deprecated() call
    return importlib.import_module("{{program:py_deprecated_module_path}}.ttypes")

_fbthrift__module_name__ = "{{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.types"

__all__ = []

### Enums ###
{{#program:enums}}

{{enum:name}} = _fbthrift_python_enums.{{enum:name}}

{{#program:inplace_migrate?}}
__all__.append("{{enum:name}}")

{{/program:inplace_migrate?}}
{{/program:enums}}

### Union Enums ###
{{#program:filtered_structs}}
{{#struct:union?}}

{{> inplace/union_enum_py }}


{{! this is a dumb hack due to dunder class mangling rules}}
_fbthrift__{{struct:name}}Type = __{{struct:name}}Type

{{#program:inplace_migrate?}}
__all__.append("__{{struct:name}}Type")

{{/program:inplace_migrate?}}
{{/struct:union?}}
{{/program:filtered_structs}}

### Containers ###
{{#program:containerTypes}}
{{> inplace/container }}

{{#program:inplace_migrate?}}
__all__.append("{{type:flat_name}}")

{{/program:inplace_migrate?}}
{{/program:containerTypes}}

### Structured Types ###
{{#program:filtered_structs}}
class {{struct:name}}({{> types/python_struct_class }}):
    __module__ = _fbthrift__module_name__
    __slots__ = (
        "_fbthrift__inner",
    {{#struct:py3_fields}}{{#field:type}}{{#type:needs_convert?}}
        "_fbthrift_inner__{{field:py_name}}",
    {{/type:needs_convert?}}{{/field:type}}{{/struct:py3_fields}}{{#struct:union?}}
        "_fbthrift_inner__type",
        "_fbthrift_inner__value",
    {{/struct:union?}}
    )
    _FBTHRIFT__PYTHON_CLASS = _fbthrift_python_types.{{struct:name}}
    _FBTHRIFT__FIELD_NAMES = (
        {{#struct:py3_fields}}
        "{{field:py_name}}",
        {{/struct:py3_fields}}
    )
    {{#struct:allow_inheritance?}}
    _fbthrift_allow_inheritance_DO_NOT_USE = True
    {{/struct:allow_inheritance?}}
    {{#struct:union?}}
    Type = _fbthrift__{{struct:name}}Type
    {{/struct:union?}}
    _fbthrift__inner : _fbthrift_python_types.{{struct:name}}
    {{#struct:py3_fields}}
    {{#field:type}}{{#type:needs_convert?}}
    _fbthrift_inner__{{field:py_name}} : {{> inplace/field_type}} | None
    {{/type:needs_convert?}}{{/field:type}}
    {{/struct:py3_fields}}
    {{#struct:union?}}
    _fbthrift_inner__type: Type
    _fbthrift_inner__value: {{> inplace/union_value_type }}
    {{/struct:union?}}


    def __init__(self, *args, **kwargs) -> None:
        try:
            self._fbthrift__inner = _fbthrift_python_types.{{struct:name}}(*args, **kwargs)
        except TypeError:
        {{! TODO(T222259754): thrift-py3 skips validation of None fields; thrift-python does not}}
            kwargs = _fbthrift__filter_kwargs(kwargs, self._FBTHRIFT__FIELD_NAMES)
            self._fbthrift__inner = _fbthrift_python_types.{{struct:name}}(*args, **kwargs)

        {{! we deliberately omit __init__ of Struct/GeneratedError, which tries to init cpp2 object}}
        {{#struct:exception?}}
        super(thrift.python.exceptions.Error, self).__init__(*(val for _, val in self))
        {{/struct:exception?}}
        {{! There's an optimization opportunity here if convertible objects are already correct type}}

    def __new__(_fbthrift__cls, *args, **kwargs) -> {{struct:name}}:
        instance = super().__new__(_fbthrift__cls)
        {{#struct:py3_fields}}
        {{#field:type}}{{#type:needs_convert?}}
        instance._fbthrift_inner__{{field:py_name}} = None
        {{/type:needs_convert?}}{{/field:type}}
        {{/struct:py3_fields}}
        {{#struct:union?}}
        instance._fbthrift_inner__type = None
        instance._fbthrift_inner__value = None
        {{/struct:union?}}
        return instance

    {{^struct:exception?}}{{^struct:union?}}{{^struct:cpp_noncopyable?}}
    def __call__(self, **kwargs) -> {{struct:name}}:
        return {{struct:name}}.from_python(self._fbthrift__inner(**kwargs))

    {{/struct:cpp_noncopyable?}}{{/struct:union?}}{{/struct:exception?}}
    @staticmethod
    def from_python(thrift_python_inner: _fbthrift_python_types.{{struct:name}}) -> {{struct:name}}:
        inst = {{struct:name}}.__new__({{struct:name}})
        inst._fbthrift__inner = thrift_python_inner
        {{#struct:exception?}}
        super(thrift.python.exceptions.Error, inst).__init__(*(val for _, val in inst))
        {{/struct:exception?}}
        return inst

    def _to_py3(self) -> {{struct:name}}:
        return self

    def _to_python(self) -> _fbthrift_python_types.{{struct:name}}:
        return self._fbthrift__inner

    def _to_py_deprecated(self):
        py_deprecated_types = _get_py_deprecated_module()
        return thrift.util.converter.to_py_struct(py_deprecated_types.{{struct:name}}, self)

    def _fbthrift__isset(self) -> dict[str, bool]:
        return thrift.python.types.isset(self._fbthrift__inner)

    @staticmethod
    def __get_reflection__():
        return get_types_reflection().get_reflection__{{struct:name}}()

    @staticmethod
    def __get_metadata__():
        return {{struct:name}}._FBTHRIFT__PYTHON_CLASS.__get_metadata__()

    @staticmethod
    def __get_thrift_name__():
        return "{{program:name}}.{{struct:name}}"

    {{#struct:py3_fields}}
    @_python__property
    def {{field:py_name}}(self) -> {{> inplace/field_type}}:
{{#field:type}}{{#type:needs_convert?}}
        if self._fbthrift_inner__{{field:py_name}} is None:
            __python_val = self._fbthrift__inner.{{field:py_name}}
            {{#field:optional?}}
            if __python_val is None:
            {{^field:optional_default?}}
                return None
            {{/field:optional_default?}}
            {{#field:optional_default?}}
                __python_val = {{#field:user_default_value}}{{#field:type}}{{> inplace/constant_value_py }}{{/field:type}}{{/field:user_default_value}}
            {{/field:optional_default?}}
            {{/field:optional?}}{{^field:optional?}}{{#struct:union?}}
            if __python_val is None:
                return None
            {{/struct:union?}}{{/field:optional?}}
            self._fbthrift_inner__{{field:py_name}} = {{!
                }}{{^type:float?}}{{> types/python_type}}.from_python{{!
                }}{{/type:float?}}{{#type:float?}}_fbthrift__round_float32{{!
                }}{{/type:float?}}(__python_val)

        return self._fbthrift_inner__{{field:py_name}}
{{/type:needs_convert?}}{{^type:needs_convert?}}{{^field:optional_default?}}
        return self._fbthrift__inner.{{field:py_name}}
{{/field:optional_default?}}{{#field:optional_default?}}
        __python_val = self._fbthrift__inner.{{field:py_name}}
        if __python_val is None:
            return {{#field:user_default_value}}{{#field:type}}{{> inplace/constant_value_py }}{{/field:type}}{{/field:user_default_value}}
        return __python_val
{{/field:optional_default?}}{{/type:needs_convert?}}{{/field:type}}

    {{/struct:py3_fields}}
    {{#struct:union?}}
    @_python__property
    def type(self) -> _fbthrift_python_types.{{struct:name}}.Type:
        if self._fbthrift_inner__type is None:
            self._fbthrift_inner__type = self.Type(self._fbthrift__inner.type.value)
        return self._fbthrift_inner__type

    @_python__property
    def value(self) -> {{> inplace/union_value_type }}:
        match self._fbthrift__inner.type:
{{#struct:py3_fields}}{{#field:type}}{{#type:needs_convert?}}
            case _fbthrift_python_types.{{struct:name}}.Type.{{field:py_name}}:
                return self.{{field:py_name}}
{{/type:needs_convert?}}{{/field:type}}{{/struct:py3_fields}}
            case _:
                return self._fbthrift__inner.value


    {{> inplace/union_from_value}}

    {{/struct:union?}}

    @classmethod
    def _fbthrift_get_field_name_by_index(cls, idx: int) -> str:
        return cls._FBTHRIFT__FIELD_NAMES[idx]

    @classmethod
    def _fbthrift_get_struct_size(cls) -> int:
        return {{struct:size}}

    def __eq__(self, other):
    {{! when in-place migration complete, we can start ignoring cpp.noncomparable attribute }}
        if type(self) != type(other):
            return False

    {{! if we didn't have to worry about float32, we could just compare the inner objects }}
        {{#struct:union?}}
        return self.type == other.type and self.value == other.value
        {{/struct:union?}}{{^struct:union?}}
        for (_, self_val), (_, other_val) in zip(self, other):
            if self_val != other_val:
                return False
        return True
        {{/struct:union?}}


    {{#struct:is_struct_orderable?}}
    def __lt__(self, other):
        if not isinstance(other, {{struct:name}}):
            return NotImplemented
        return self._fbthrift__inner < other._fbthrift__inner

    def __le__(self, other):
        if not isinstance(other, {{struct:name}}):
            return NotImplemented
        return self._fbthrift__inner <= other._fbthrift__inner

    {{/struct:is_struct_orderable?}}

    {{#struct:exception?}}
    def __str__(self):
        {{#struct:exception_message?}}
        field = self.{{struct:exception_message}}
        if field is None:
            {{! optional field, stringify }}
            return str(field)
        return field
        {{/struct:exception_message?}}
        {{^struct:exception_message?}}
        return super().__str__()
        {{/struct:exception_message?}}

    {{/struct:exception?}}
    def __hash__(self):
        return super().__hash__()

    def __copy__(self):
        return self{{! thrift-py3 / thrift-python is immutable}}


{{#program:inplace_migrate?}}
__all__.append("{{struct:name}}")

{{/program:inplace_migrate?}}
{{/program:filtered_structs}}

### Constants
{{#program:constants}}{{#constant:value}}
{{constant:name}} = {{> inplace/constant_value_py }}
{{/constant:value}}{{/program:constants}}
{{/program:inplace_migrate?}}
